from datetime import datetime, date
from typing import Optional

from sqlalchemy import select, or_
from sqlalchemy.ext.asyncio import AsyncSession

from app.core.security import get_password_hash, verify_password
from app.crud.base import CRUDBase
from app.models.user import User, UserType, VerificationStatus
from app.schemas.user import UserCreate, UserUpdate


class CRUDUser(CRUDBase[User, UserCreate, UserUpdate]):
    async def get_by_email(self, db: AsyncSession, *, email: str) -> Optional[User]:
        result = await db.execute(select(User).filter(User.email == email))
        return result.scalars().first()

    async def get_by_username(self, db: AsyncSession, *, username: str) -> Optional[User]:
        result = await db.execute(select(User).filter(User.username == username))
        return result.scalars().first()

    async def get_by_phone(self, db: AsyncSession, *, phone: str) -> Optional[User]:
        result = await db.execute(select(User).filter(User.phone == phone))
        return result.scalars().first()

    async def get_by_document_number(
        self, db: AsyncSession, *, document_number: str
    ) -> Optional[User]:
        result = await db.execute(select(User).filter(User.document_number == document_number))
        return result.scalars().first()

    async def get_by_identifier(
        self,
        db: AsyncSession,
        *,
        identifier: str,
        user_type: Optional[UserType] = None,
    ) -> Optional[User]:
        stmt = select(User).filter(
            or_(User.phone == identifier, User.document_number == identifier)
        )
        if user_type:
            stmt = stmt.filter(User.user_type == user_type)
        result = await db.execute(stmt)
        return result.scalars().first()

    async def create(self, db: AsyncSession, *, obj_in: UserCreate) -> User:
        """Create user with hashed password."""
        db_obj = User(
            phone=obj_in.phone,
            email=obj_in.email,
            username=obj_in.username,
            hashed_password=get_password_hash(obj_in.password),
            user_type=obj_in.user_type,
            verification_status=obj_in.verification_status,
            is_active=obj_in.is_active,
            is_superuser=obj_in.is_superuser,
            nickname=obj_in.nickname,
            avatar=obj_in.avatar,
            full_name=obj_in.full_name,
            document_type=obj_in.document_type,
            document_number=obj_in.document_number,
            gender=obj_in.gender,
            birth_date=obj_in.birth_date,
            agreed_at=obj_in.agreed_at or datetime.utcnow(),
        )
        db_obj.age = self._calculate_age(db_obj.birth_date)
        db.add(db_obj)
        await db.flush()
        await db.refresh(db_obj)
        return db_obj

    async def update(
        self,
        db: AsyncSession,
        *,
        db_obj: User,
        obj_in: UserUpdate,
    ) -> User:
        """Update user handling password hashing and derived fields."""
        update_data = obj_in.model_dump(exclude_unset=True)

        if "password" in update_data:
            update_data["hashed_password"] = get_password_hash(update_data.pop("password"))

        if "birth_date" in update_data:
            birth_date = update_data["birth_date"]
            update_data["age"] = self._calculate_age(birth_date)

        return await super().update(db, db_obj=db_obj, obj_in=update_data)

    async def update_last_login(self, db: AsyncSession, *, db_obj: User) -> User:
        db_obj.last_login_at = datetime.utcnow()
        db.add(db_obj)
        await db.flush()
        await db.refresh(db_obj)
        return db_obj

    def authenticate(self, db_user: User, password: str) -> bool:
        if not db_user or not db_user.is_active:
            return False
        return verify_password(password, db_user.hashed_password)

    def is_active(self, user: User) -> bool:
        if user.user_type == UserType.DOCTOR:
            return user.is_active and user.verification_status == VerificationStatus.APPROVED
        return user.is_active

    def is_superuser(self, user: User) -> bool:
        return user.is_superuser

    @staticmethod
    def _calculate_age(birth_date: Optional[date]) -> Optional[int]:
        if not birth_date:
            return None
        today = date.today()
        age = today.year - birth_date.year - (
            (today.month, today.day) < (birth_date.month, birth_date.day)
        )
        return max(age, 0)


user = CRUDUser(User)
